/**
 * CoffeeMilk (C) 2010 Amen Hamdan
 * 
 * See the NOTICE file distributed with this work for additional 
 * information regarding copyright ownership.  This file is licenses
 * to you under the Apache License, Version 2.0 (the "License"); you 
 * may not use this file except in compliance with the License.  You 
 * may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 * implied. See the License for the specific language governing 
 * permissions and limitations under the License.    
 * 
 */
package ameen.rtm.lib;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;

import ameen.rtm.lib.api.Methods;
import ameen.rtm.lib.request.RtmRequestHandler;
import ameen.rtm.lib.request.RtmRequestParameters;
import ameen.rtm.lib.types.AuthToken;
import ameen.rtm.lib.types.RtmDataFactory;
import ameen.rtm.lib.types.RtmList;
import ameen.rtm.lib.types.RtmTask;
import ameen.rtm.lib.types.RtmTaskNote;
import ameen.rtm.lib.types.TasksVector;


public class RtmService implements PropertyChangeListener {
	private RtmRequestHandler fRtmRequestHandler;
	private RtmServiceSettings fSettings;
	private Vector<RtmList> fListsCache;
	
	public enum AccessPermission {
		READ 	{ public String toString() { return "read"; } },
		WRITE 	{ public String toString() { return "write"; } },
		DELETE 	{ public String toString() { return "delete"; } };
	}
	
	public RtmService(RtmServiceSettings rtmSettings) {
		fSettings = rtmSettings;
		fRtmRequestHandler = new RtmRequestHandler(fSettings.getApiKey(), fSettings.getSharedSecret());
	}

	public String getFrob() throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.auth.getFrob);
		JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);
		return node.get("frob").getTextValue();	
	}

	public AuthToken getToken(String frob) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.auth.getToken);
		rtmParameters.put("frob", frob);
		
		JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);
		return new AuthToken(node);
	}

	public String updateTimeLine() throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.timelines.create);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);	
		String timeline = node.get("timeline").getTextValue();
		fSettings.setTimeline(timeline);
		
		return timeline;
	}
	
	public void createTask(String taskName) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.tasks.add);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("name", taskName);
		rtmParameters.put("parse", "1");
		
		JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);	
		RtmTask task = RtmDataFactory.createRtmTask(node.get("list").get("taskseries"));
		String listId = node.get("list").get("id").getTextValue();
		task.setListId(listId);
	
		for(RtmList currentList : fListsCache) {
			if(currentList.getId().equals(listId)) {
				currentList.addTask(task);
			}
		}
		task.addPropertyChangeListener(this);
	}
	
	public void createNote(RtmTask rtmTask, String noteTitle, String noteBody) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.notes.add);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("list_id", rtmTask.getListId());
		rtmParameters.put("taskseries_id", rtmTask.getTaskSeriesId());
		rtmParameters.put("task_id", rtmTask.getId());	
		rtmParameters.put("note_title", noteTitle);
		rtmParameters.put("note_text", noteBody);
	
		JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);	
		RtmTaskNote newNote = RtmDataFactory.createNote(node.get("note"));
		rtmTask.getNotes().add(newNote);
	}	
	
	public void deleteNote(RtmTask rtmTask, RtmTaskNote note) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.notes.delete);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("list_id", rtmTask.getListId());
		rtmParameters.put("taskseries_id", rtmTask.getTaskSeriesId());
		rtmParameters.put("task_id", rtmTask.getId());	
		rtmParameters.put("note_id", note.getId());
		fRtmRequestHandler.rtmCall(rtmParameters);	
	}	

	
	public void updateNote(RtmTask rtmTask, RtmTaskNote note) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.notes.edit);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("list_id", rtmTask.getListId());
		rtmParameters.put("taskseries_id", rtmTask.getTaskSeriesId());
		rtmParameters.put("task_id", rtmTask.getId());	
		rtmParameters.put("note_id", note.getId());
		rtmParameters.put("note_title", note.getTitle());
		rtmParameters.put("note_text", note.getText());
		
		fRtmRequestHandler.rtmCall(rtmParameters);	
	}	
	
	public AuthToken checkToken(AuthToken authToken) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.auth.checkToken);
		rtmParameters.put("auth_token", authToken.getToken());
		try {
			JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);
			return new AuthToken(node);
		} catch (RtmException rtme) {
		}
		return null;
	}

	public Map<String, TasksVector> getTaskList() throws RtmException {
		Map<String, TasksVector> retMap = new HashMap<String, TasksVector>();
		
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.tasks.getList);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);
	
		ArrayNode arrayNode = (ArrayNode) node.get("tasks").get("list");
		for(int i=0; i<arrayNode.size(); i++) {
			JsonNode listNode = arrayNode.get(i);
			String listId = listNode.get("id").getTextValue();
			JsonNode taskSeriesNode = listNode.get("taskseries");
			
			TasksVector taskVector = new TasksVector(); 
			if(taskSeriesNode instanceof ArrayNode) {
				ArrayNode taskArray = (ArrayNode) taskSeriesNode;
				for(int j=0; j<taskArray.size(); j++) {
					JsonNode taskNode = taskArray.get(j);
					RtmTask newRtmTask = RtmDataFactory.createRtmTask(taskNode); 
					newRtmTask.setListId(listId);
					newRtmTask.addPropertyChangeListener(this);
					
					if(!newRtmTask.isCompleted()) taskVector.add(newRtmTask);
				}
			} 
			retMap.put(listId, taskVector);
		}	
	
		return retMap;
	}
	
	public Vector<RtmList> getLists() throws RtmException {
		Vector<RtmList> retVector = new Vector<RtmList>();
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.lists.getList);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		
		JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);
		ArrayNode arrayNode = (ArrayNode) node.get("lists").get("list");
		for(int i=0; i<arrayNode.size(); i++) {
			JsonNode currentNode = arrayNode.get(i);
			RtmList newEntry = RtmDataFactory.createRtmList(currentNode);
			if(!newEntry.isSmart()) {
				newEntry.addPropertyChangeListener(this);
				retVector.add(newEntry);
			}
		}

		fListsCache = retVector;
		return retVector;	}

	public void updatePriority(RtmTask rtmTask) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.tasks.setPriority);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("list_id", rtmTask.getListId());
		rtmParameters.put("taskseries_id", rtmTask.getTaskSeriesId());
		rtmParameters.put("task_id", rtmTask.getId());
		rtmParameters.put("priority", "" + rtmTask.getPriority());
		fRtmRequestHandler.rtmCall(rtmParameters);
	}

	public void completeTask(RtmTask rtmTask) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.tasks.complete);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("list_id", rtmTask.getListId());
		rtmParameters.put("taskseries_id", rtmTask.getTaskSeriesId());
		rtmParameters.put("task_id", rtmTask.getId());
		fRtmRequestHandler.rtmCall(rtmParameters);
	}

	public void updateDueDate(RtmTask rtmTask) throws RtmException {
		if(rtmTask.getNewDueDate() == null) return;
		
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.tasks.setDueDate);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("list_id", rtmTask.getListId());
		rtmParameters.put("taskseries_id", rtmTask.getTaskSeriesId());
		rtmParameters.put("task_id", rtmTask.getId());
		rtmParameters.put("due", rtmTask.getNewDueDate());
		rtmParameters.put("parse", "1");
		
		JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);
		RtmTask task = RtmDataFactory.createRtmTask(node.get("list").get("taskseries"));
		rtmTask.setDueDate(task.getDueDate());
		rtmTask.clearNewDueDate();
	}

	public void updateTags(RtmTask rtmTask) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.tasks.setTags);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("list_id", rtmTask.getListId());
		rtmParameters.put("taskseries_id", rtmTask.getTaskSeriesId());
		rtmParameters.put("task_id", rtmTask.getId());
		rtmParameters.put("tags", rtmTask.getNewTagsString());
		
		JsonNode node = fRtmRequestHandler.rtmCall(rtmParameters);
		RtmTask task = RtmDataFactory.createRtmTask(node.get("list").get("taskseries"));
		rtmTask.setTags(task.getTags());
		rtmTask.clearNewTags();
	}	
	
	public void updateName(RtmTask rtmTask) throws RtmException {
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.tasks.setDueDate);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("list_id", rtmTask.getListId());
		rtmParameters.put("taskseries_id", rtmTask.getTaskSeriesId());
		rtmParameters.put("task_id", rtmTask.getId());
		rtmParameters.put("name", rtmTask.getName());
		
		fRtmRequestHandler.rtmCall(rtmParameters);
	}	
	
	public void moveTask(RtmTask rtmTask) throws RtmException {
		if(rtmTask.getTargetMoveList() == null) return;
		
		RtmRequestParameters rtmParameters = new RtmRequestParameters(Methods.tasks.moveTo);
		rtmParameters.put("auth_token", fSettings.getAuthToken().getToken());
		rtmParameters.put("timeline", fSettings.getTimeline());
		rtmParameters.put("from_list_id", rtmTask.getListId());
		rtmParameters.put("to_list_id", rtmTask.getTargetMoveList().getId());
		rtmParameters.put("taskseries_id", rtmTask.getTaskSeriesId());
		rtmParameters.put("task_id", rtmTask.getId());
		
		fRtmRequestHandler.rtmCall(rtmParameters);
		for(RtmList rtmList : fListsCache) {
			if(rtmList.getId().equals(rtmTask.getTargetMoveList().getId())) {
				rtmList.addTask(rtmTask);
			}
			if(rtmList.getId().equals(rtmTask.getListId())) {
				rtmList.removeTask(rtmTask);
			}
		}
		
		rtmTask.setListId(rtmTask.getTargetMoveList().getId());
		rtmTask.cleanMoveTargetList();
	}

	public String createAuthUrl(String frob, AccessPermission accessPerms) {
		return fRtmRequestHandler.createAuthUrl(frob, accessPerms);
	}

	public boolean checkSettings() {
		if(fSettings.getFrob() == null) return false;
		
		AuthToken authToken = fSettings.getAuthToken();

		if(authToken == null) {
			try {
				authToken = checkToken(authToken);
				
				if(authToken == null) authToken = getToken(fSettings.getFrob());
				if(authToken == null) return false; 
				fSettings.setAuthToken(authToken);
				
			} catch (RtmException e) {
				e.printStackTrace();
				return false;
			}
		}
		
		if(fSettings.getTimeline() == null) {
			try {
				updateTimeLine();
			} catch (RtmException e) {
				e.printStackTrace();
			}
		}
		return true;
	}
	
	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		if(evt.getSource() instanceof RtmTask) {
			RtmTask rtmTask = (RtmTask) evt.getSource();
			try {
				if(evt.getPropertyName() == RtmTask.PRIORITY) {
					updatePriority(rtmTask);
					
				} else if(evt.getPropertyName() == RtmTask.NAME) {
					updateName(rtmTask);
					
				} else if(evt.getPropertyName() == RtmTask.NEW_DUEDATE) {
					updateDueDate(rtmTask);
					
				} else if(evt.getPropertyName() == RtmTask.COMPLETED) {
					completeTask(rtmTask);
					
				} else if(evt.getPropertyName() == RtmTask.NEW_LIST) {
					moveTask(rtmTask);
					
				} else if(evt.getPropertyName() == RtmTask.NEW_TAGS) {
					updateTags(rtmTask);
					
				} else if(evt.getPropertyName() == RtmTask.NEW_NOTE) {
					String[] parameters = (String[]) evt.getNewValue();
					createNote(rtmTask, parameters[0], parameters[1]);
					
				} else if(evt.getPropertyName() == RtmTask.DELETE_NOTE) {
					RtmTaskNote deletedNote = (RtmTaskNote) evt.getOldValue();
					deleteNote(rtmTask, deletedNote);
					
				} else if(evt.getPropertyName() == RtmTask.NOTE_UPDATE) {
					RtmTaskNote note = (RtmTaskNote) evt.getNewValue();
					updateNote(rtmTask, note);
				}
				
			} catch (RtmException e) {
				e.printStackTrace();
			}
		}
	}
}
